package server;

import java.io.IOException;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;
import java.util.SortedMap;
import java.util.TreeMap;

public class GameData {
	private Map<PlayerThread, Integer> playerpoints;
	private Map<PlayerThread, Integer[]> playerposition; // [0] = x, [1] = y,[2]
															// =
															// facing(0=up,1=right,2=down,3=left)
	private Bonus bonus;
	private Iterator<String> playerColors;
	private SortedMap<Date, PlayerThread> playerQueue;

	private static GameData uniqueInstance;

	private GameData() {
		playerpoints = new HashMap<PlayerThread, Integer>();
		playerposition = new HashMap<PlayerThread, Integer[]>();
		String[] colors = new String[] { "1", "2", "3", "4", "5", "6" };
		playerColors = Arrays.asList(colors).iterator();
		playerQueue = new TreeMap<Date, PlayerThread>();
	}
	
	public boolean canEnter(PlayerThread pt) {
		if(playerQueue.isEmpty()) {
			return true;
		} else {
			playerQueue.put(new Date(), pt);
			return false;
		}
	}
	
	public void releaseCritical() {
		if(!playerQueue.isEmpty()) {
			playerQueue.remove(playerQueue.firstKey());
			playerQueue.get(playerQueue.firstKey()).notify();
		}
	}

	public static GameData getUnqiueInstance() {
		if (uniqueInstance == null) {
			uniqueInstance = new GameData();
		}
		return uniqueInstance;
	}

	public void addPlayer(PlayerThread player) {
		playerpoints.put(player, 0);
		playerposition.put(player, new Integer[] { 9, 7, 0 }); // TODO se player
																// constructor i
																// client, låst
																// setup.
	}

	public void setPlayerPoints(PlayerThread player, int points) {
		playerpoints.put(player, points);
	}

	public void setPlayerPosition(PlayerThread player, Integer[] position) {
		if (position.length != 2) {
			throw new RuntimeException("Position array must be of length 2.");
		}

		playerposition.put(player, position);
	}

	/**
	 * Beregner en spillers nye position og score
	 * 
	 * @param pt
	 * @param facing
	 */
	public void calculateMove(PlayerThread pt, int facing) {
		Integer[] pos = playerposition.get(pt);
		int x = pos[0];
		int y = pos[1];
		if (facing == 1) { // facing(0=up,1=right,2=down,3=left)
			x = x + 1;
		} else if (facing == 3) {
			x = x - 1;
		} else if (facing == 0) {
			y = y - 1;
		} else if (facing == 2) {
			y = y + 1;
		}
		int pts = playerpoints.get(pt);
		try {
			if (level[x][y].equals(wall)) {
				playerpoints.put(pt, pts - 1);
				Server.getInstance().broadcastPoints(pt.getPlayerName(), playerpoints.get(pt));
			} else {
				playerpoints.put(pt, pts + 1);
				playerposition.put(pt, new Integer[] { x, y, facing });
				Server.getInstance().broadcastMove(pt.getPlayerName(), playerposition.get(pt));
				PlayerThread stepee = calculateStepOn(pt);
				if (stepee != null) {
					playerpoints.put(pt, playerpoints.get(pt) + 50);
					playerpoints.put(stepee, playerpoints.get(stepee) - 50);
					Server.getInstance().broadcastPoints(stepee.getPlayerName(),
													playerpoints.get(stepee));
				}
				Server.getInstance().broadcastPoints(pt.getPlayerName(), playerpoints.get(pt));
			}
		} catch (IOException ioe) {
			// TODO dis is bad
		}
	}

	private PlayerThread calculateStepOn(PlayerThread stepper) {
		PlayerThread stepee = null;
		Set<PlayerThread> targetSet = new HashSet<PlayerThread>(playerposition.keySet());
		targetSet.remove(stepper);
		Integer[] s_pos = getPositions(stepper);
		for (PlayerThread pt : targetSet) {
			Integer[] t_pos = getPositions(pt);
			if (s_pos[0] == t_pos[0] && s_pos[1] == t_pos[1]) {
				stepee = pt;
			}
		}
		return stepee;
	}

	private String wall = "w";
	private String[][] level = {
									{ "w", "w", "w", "w", "w", "w", "w", "w", "w", "w", "w", "w",
																	"w", "w", "w", "w", "w", "w",
																	"w", "w" },
									{ "w", "e", "e", "e", "e", "e", "e", "e", "e", "w", "w", "e",
																	"e", "e", "e", "e", "e", "e",
																	"e", "w" },
									{ "w", "e", "w", "e", "e", "w", "e", "e", "w", "w", "w", "e",
																	"w", "e", "e", "w", "e", "e",
																	"w", "w" },
									{ "w", "e", "w", "e", "e", "w", "e", "e", "e", "w", "w", "e",
																	"w", "e", "e", "w", "e", "e",
																	"w", "w" },
									{ "w", "e", "e", "w", "e", "e", "e", "e", "e", "e", "e", "e",
																	"e", "e", "e", "e", "e", "e",
																	"e", "w" },
									{ "w", "e", "w", "e", "w", "e", "w", "e", "w", "e", "w", "e",
																	"w", "e", "e", "w", "e", "e",
																	"w", "w" },
									{ "w", "e", "w", "e", "e", "e", "e", "e", "w", "w", "w", "e",
																	"w", "e", "e", "w", "e", "e",
																	"w", "w" },
									{ "w", "e", "w", "e", "e", "e", "e", "e", "w", "e", "w", "e",
																	"w", "e", "e", "w", "e", "e",
																	"w", "w" },
									{ "w", "e", "e", "e", "w", "e", "w", "e", "e", "w", "e", "e",
																	"w", "e", "e", "w", "e", "e",
																	"e", "w" },
									{ "w", "e", "e", "e", "e", "e", "w", "e", "e", "w", "e", "e",
																	"w", "e", "e", "w", "e", "e",
																	"e", "w" },
									{ "w", "e", "w", "w", "e", "w", "w", "e", "e", "e", "e", "e",
																	"e", "e", "e", "w", "e", "e",
																	"w", "w" },
									{ "w", "e", "e", "w", "e", "w", "e", "e", "e", "e", "w", "e",
																	"e", "e", "e", "w", "e", "e",
																	"w", "w" },
									{ "w", "e", "e", "e", "e", "e", "e", "e", "e", "w", "w", "e",
																	"w", "e", "e", "w", "e", "e",
																	"w", "w" },
									{ "w", "e", "e", "e", "e", "e", "e", "e", "e", "e", "w", "e",
																	"w", "e", "e", "w", "e", "e",
																	"w", "w" },
									{ "w", "e", "e", "e", "e", "e", "e", "e", "e", "w", "e", "e",
																	"e", "e", "e", "w", "e", "e",
																	"w", "w" },
									{ "w", "e", "e", "w", "e", "e", "e", "e", "e", "e", "e", "e",
																	"e", "e", "e", "e", "e", "e",
																	"w", "w" },
									{ "w", "e", "e", "w", "e", "w", "w", "w", "e", "e", "w", "e",
																	"w", "e", "e", "w", "w", "e",
																	"w", "w" },
									{ "w", "e", "w", "e", "e", "e", "e", "e", "e", "w", "w", "e",
																	"w", "e", "e", "e", "e", "e",
																	"w", "w" },
									{ "w", "e", "e", "e", "w", "e", "e", "e", "w", "w", "e", "e",
																	"w", "e", "e", "e", "e", "e",
																	"e", "w" },
									{ "w", "w", "w", "w", "w", "w", "w", "w", "w", "w", "w", "w",
																	"w", "w", "w", "w", "w", "w",
																	"w", "w" }, };

	public Integer[] getPositions(PlayerThread pt) {
		Integer[] pos = playerposition.get(pt);
		Integer[] r = new Integer[] { pos[0], pos[1], pos[2] };
		return r;
	}

	public void calculateShot(PlayerThread pt) {
		PlayerThread target = findTarget(pt);
		if (target != null) {
			try {
				if (!blockedPath(pt, target)) {
					int points = 0;
					if (playerpoints.get(target) > 0) {
						points = playerpoints.get(target) / 3;
					}
					playerpoints.put(target, playerpoints.get(target) - points);
					playerpoints.put(pt, playerpoints.get(pt) + points);
					Server.getInstance().broadcastPoints(pt.getPlayerName(), playerpoints.get(pt));
					Server.getInstance().broadcastPoints(target.getPlayerName(),
													playerpoints.get(target));
					Server.getInstance().broadcastShot(playerposition.get(pt),
													playerposition.get(target));
				} else {
					Server.getInstance().broadcastShot(playerposition.get(pt), null);
					System.out.println("calculate shot failed, no target found"); // TODO
																					// debug
				}
			} catch (IOException e) {
				// TODO this is bad
			}
		} else {
			Server.getInstance().broadcastShot(playerposition.get(pt), null);
		}
	}

	/**
	 * Determines if the shot isn't obstructed by a wall
	 * 
	 * @param target
	 * @return
	 */
	private boolean blockedPath(PlayerThread shooter, PlayerThread target) {
		boolean failed = false;
		Integer[] pos_sho = getPositions(shooter);
		Integer[] pos_tar = getPositions(target);

		if (pos_sho[0] == pos_tar[0]) {
			if (pos_sho[1] > pos_tar[1]) {
				int y = pos_sho[1];
				while (y > pos_tar[1] && !failed) {
					if (level[pos_sho[0]][y].equals(wall)) {
						failed = true;
					} else {
						y--;
					}
				}
			} else if (pos_sho[1] < pos_tar[1]) {
				int y = pos_sho[1];
				while (y < pos_tar[1] && !failed) {
					if (level[pos_sho[0]][y].equals(wall)) {
						failed = true;
					} else {
						y++;
					}
				}
			}
		} else if (pos_sho[1] == pos_tar[1]) {
			if (pos_sho[0] < pos_tar[0]) {
				int x = pos_sho[0];
				while (x < pos_tar[0] && !failed) {
					if (level[x][pos_sho[1]].equals(wall)) {
						failed = true;
					} else {
						x++;
					}
				}
			} else if (pos_sho[0] > pos_tar[0]) {
				int x = pos_sho[0];
				while (x > pos_tar[0] && !failed) {
					if (level[x][pos_sho[1]].equals(wall)) {
						failed = true;
					} else {
						x--;
					}
				}
			}
		}
		System.out.println("Pathfinding failed: " + failed);
		return failed;
	}

	// [0] = x, [1] = y, [2] = facing(0=up,1=right,2=down,3=left)
	/**
	 * finds the closest target in the direction or null if not found.
	 * 
	 * @param pos
	 * @return
	 */
	private PlayerThread findTarget(PlayerThread shooter) {
		Integer[] pos = getPositions(shooter);
		PlayerThread target = null;
		int min_dist = Integer.MAX_VALUE;
		Set<PlayerThread> targetSet = new HashSet<PlayerThread>(playerposition.keySet());
		targetSet.remove(shooter);
		Iterator<PlayerThread> targets = targetSet.iterator();
		// facing(0=up,1=right,2=down,3=left)
		if (pos[2] == 0) {
			while (targets.hasNext()) {
				PlayerThread pt_tar = targets.next();
				Integer[] tar = getPositions(pt_tar);
				if (pos[0] == tar[0]) {
					int distance = pos[1] - tar[1];
					if (distance > 0 && distance < min_dist) {
						target = pt_tar;
						min_dist = distance;
					}
				}
			}
		} else if (pos[2] == 1) {
			while (targets.hasNext()) {
				PlayerThread pt_tar = targets.next();
				Integer[] tar = getPositions(pt_tar);
				if (pos[1] == tar[1]) {
					int distance = tar[0] - pos[0];
					if (distance > 0 && distance < min_dist) {
						target = pt_tar;
						min_dist = distance;
					}
				}
			}
		} else if (pos[2] == 2) {
			while (targets.hasNext()) {
				PlayerThread pt_tar = targets.next();
				Integer[] tar = getPositions(pt_tar);
				if (pos[0] == tar[0]) {
					int distance = tar[1] - pos[1];
					if (distance > 0 && distance < min_dist) {
						target = pt_tar;
						min_dist = distance;
					}
				}
			}

		} else if (pos[2] == 3) {
			while (targets.hasNext()) {
				PlayerThread pt_tar = targets.next();
				Integer[] tar = getPositions(pt_tar);
				if (pos[1] == tar[1]) {
					int distance = pos[0] - tar[0];
					if (distance > 0 && distance < min_dist) {
						target = pt_tar;
						min_dist = distance;
					}
				}
			}
		}
		System.out.println("Target candidate found:" + target);
		return target;
	}

	public int getPlayerpoints(PlayerThread pt) {
		return playerpoints.get(pt);
	}

	public String getNextColor() throws NoSuchElementException {
		return playerColors.next();
	}
}